package sketch.shape.geom;

import java.awt.Graphics2D;

import sketch.shape.Shape;
import sketch.shape.ShapePredicate;
import sketch.shape.geom.prim.Line;
import sketch.shape.geom.prim.Point;
import sketch.shape.geom.prim.gVector;

public class Triangle extends Shape {

	private Point p[];

	public Triangle() {
		p = new Point[3];
		for (int i = 0; i < p.length; i++)
			p[i] = new Point();
	}

	public Triangle(Point p1, Point p2, Point p3) {
		p = new Point[3];
		p[0] = p1;
		p[1] = p2;
		p[2] = p3;
	}

	public Point getP1() {
		return p[0];
	}

	public void setP1(Point p1) {
		this.p[0] = p1.clone();
	}

	public Point getP2() {
		return p[1];
	}

	public void setP2(Point p2) {
		this.p[1] = p2.clone();
	}

	public Point getP3() {
		return p[2];
	}

	public void setP3(Point p3) {
		this.p[2] = p3.clone();
	}

	public Line getL1() {
		return new Line(getP2(), getP3());
	}

	public Line getL2() {
		return new Line(getP3(), getP1());
	}

	public Line getL3() {
		return new Line(getP1(), getP2());
	}

	public double getL1Length() {
		return getP2().distance(getP3());
	}

	public double getL2Length() {
		return getP3().distance(getP1());
	}

	public double getL3Length() {
		return getP1().distance(getP2());
	}

	public double getX1() {
		return p[0].getX();
	}

	public double getY1() {
		return p[0].getY();
	}

	public double getX2() {
		return p[1].getX();
	}

	public double getY2() {
		return p[1].getY();
	}

	public double getX3() {
		return p[2].getX();
	}

	public double getY3() {
		return p[2].getY();
	}

	private Point getPointOf(int i) {
		return p[(i+3) % 3];
	}

	public double getAngleOf(Point point) {
		int i = getCommonPointIndexOf(point);
		if (i > -1) {
			Line l1 = new Line(getPointOf(i), getSideP1Of(i));
			Line l2 = new Line(getPointOf(i), getSideP2Of(i));
			return l1.angleWith(l2);
		}
		return 0;
	}

	private Point getSideP1Of(int i) {
		return p[(i+2) % 3];
	}

	private Point getSideP2Of(int i) {
		return p[(i+1) % 3];
	}

	public Point getSideP1Of(Point point) {
		int i = getCommonPointIndexOf(point);
		return getSideP1Of(i);
	}

	public Point getSideP2Of(Point point) {
		int i = getCommonPointIndexOf(point);
		return getSideP2Of(i);
	}

	public Line getSideL1Of(Point point) {
		return new Line(point, getSideP1Of(point));
	}

	public Line getSideL2Of(Point point) {
		return new Line(point, getSideP2Of(point));
	}

	public Line getFaceOf(Point point) {
		return new Line(getSideP1Of(point), getSideP2Of(point));
	}

	public Point getCommonPointTo(Line line) {
		if (ShapePredicate.joinsLinePoint(line, getP1()))
			return getP1();
		if (ShapePredicate.joinsLinePoint(line, getP2()))
			return getP2();
		if (ShapePredicate.joinsLinePoint(line, getP3()))
			return getP3();
		return new Point();
	}

	private int getCommonPointIndexOf(Point point) {
		if (ShapePredicate.nearPointPoint(getP1(), point))
			return 0;
		if (ShapePredicate.nearPointPoint(getP2(), point))
			return 1;
		if (ShapePredicate.nearPointPoint(getP3(), point))
			return 2;
		return -1;
	}

	public Point getCommonPointTo(Point point) {
		return p[getCommonPointIndexOf(point)];
	}

	public void drawComponent(Graphics2D g2) {
		super.drawComponent(g2);
		g2.drawLine(getP1().intX(), getP1().intY(), getP2().intX(), getP2().intY());
		g2.drawLine(getP3().intX(), getP3().intY(), getP2().intX(), getP2().intY());
		g2.drawLine(getP1().intX(), getP1().intY(), getP3().intX(), getP3().intY());
	}

	public String[][] getFieldValues() {
		return new String[][] {
				{ "P1", getP1().toString() },
				{ "P2", getP2().toString() },
				{ "P3", getP3().toString() }
		};
	}

	public java.awt.Shape getBound(double GAP) {
		Point p1 = getP1().add(getAngleBisectorDirOf(0).reverse().multiply(GAP));
		Point p2 = getP2().add(getAngleBisectorDirOf(1).reverse().multiply(GAP));
		Point p3 = getP3().add(getAngleBisectorDirOf(2).reverse().multiply(GAP));
		return new java.awt.Polygon(
				new int[] {p1.intX(), p2.intX(), p3.intX()},
				new int[] {p1.intY(), p2.intY(), p3.intY()},
				3
		);
	}

	private gVector getAngleBisectorDirOf(int i) {
		Point p = getPointOf(i);
    	Point p1 = getSideP1Of(i);
    	Point p2 = getSideP2Of(i);

    	return p2.subtract(p).dir().unitized()
    			.add(p1.subtract(p).dir().unitized());
	}

	public Point getCenter() {
		double cosA = Math.cos(getAngle1());
        double cosB = Math.cos(getAngle2());
        double cosC = Math.cos(getAngle3());
        return toCartesian(cosA, cosB, cosC);
	}

    // converts trilinear coordinates to Cartesian coordinates relative
    // to the incenter; thus, the incenter has coordinates (0.0, 0.0)
    public Point toCartesian(double alpha, double beta, double gamma) {
        double area = getArea();
        double as = getL1Length(), bs = getL2Length(), cs = getL3Length();
        double r = 2 * area / (as + bs + cs);
        double k = 2 * area / (as*alpha + bs*beta + cs*gamma);
        double cosC = Math.cos(getAngle3()), sinC = Math.sin(getAngle3());
        double x = (k*beta - r + (k*alpha - r)*cosC) / sinC;
        double y = k*alpha - r;
        return new Point(x, y);
    }

    public Circle getCircumcircle() {
        return new Circle(getCircumcircleCenter(), getCircumcircleRadius());
    }

    public double getCircumcircleRadius() {
    	 double as = getL1Length(), bs = getL2Length(), cs = getL3Length();
         double s = 0.5 * (as + bs + cs);
         return (as * bs * cs) / (4 * Math.sqrt(
             s * (as + bs - s) * (as + cs - s) * (bs + cs - s)));
    }

    public Point getCircumcircleCenter() {
    	double cosA = Math.cos(getAngle1());
        double cosB = Math.cos(getAngle2());
        double cosC = Math.cos(getAngle3());
        return getIncircleCenter().add(toCartesian(cosA, cosB, cosC));
    }

    public Circle getIncircle() {
        return new Circle(getIncircleCenter(), getIncircleRadius());
    }

    public double getIncircleRadius() {
    	double as = getL1Length(), bs = getL2Length(), cs = getL3Length();
        double semiperimeter = 0.5 * (as + bs + cs);
        return getArea() / semiperimeter;
    }

    public Point getIncircleCenter() {
    	Line l1 = getAngleBisectorOf(0);
    	Line l2 = getAngleBisectorOf(1);
    	return l1.intersection(l2);
    }

    private Line getAngleBisectorOf(int i) {
    	Point p = getPointOf(i);
    	Point p1 = getSideP1Of(i);
    	Point p2 = getSideP2Of(i);

    	double scale = Math.max(p1.distance(p), p2.distance(p));
     	return new Line(p, getAngleBisectorDirOf(i).multiply(scale));
    }

    public Line getAngleBisectorOf(Point p) {
    	int i = getCommonPointIndexOf(p);
    	if (i == -1)
    		return new Line();
    	else
    		return getAngleBisectorOf(i);
    }

    // calculate the signed area
    double getSignedArea() {
        double signedArea = 0.5 * (getX1() * (getY2() - getY3()) +
                                   getX2() * (getY3() - getY1()) +
                                   getX3() * (getY1() - getY2()));
        return signedArea;
    }

    // calculate the absolute area
    public double getArea() {
        return Math.abs(getSignedArea());
    }

    // calculate the sum of the lengths of the sides
    public double getPerimeter() {
        double as = getL1Length(), bs = getL2Length(), cs = getL3Length();
        return (as + bs + cs);
    }

    public double getAngle1() {
    	double as = getL1Length(), bs = getL2Length(), cs = getL3Length();
        double angle = Math.acos((bs*bs + cs*cs - as*as) / (2*bs*cs));
        return angle;
    }

    public double getAngle2() {
    	double as = getL1Length(), bs = getL2Length(), cs = getL3Length();
        double angle = Math.acos((as*as + cs*cs - bs*bs) / (2*as*cs));
        return angle;
    }

    public double getAngle3() {
    	double as = getL1Length(), bs = getL2Length(), cs = getL3Length();
        double angle = Math.acos((as*as + bs*bs - cs*cs) / (2*as*bs));
        return angle;
    }


}
